{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# The MIT License (MIT)\n",
    "\n",
    "# Copyright (c) 2020, NVIDIA CORPORATION.\n",
    "\n",
    "# Permission is hereby granted, free of charge, to any person obtaining a copy of\n",
    "# this software and associated documentation files (the \"Software\"), to deal in\n",
    "# the Software without restriction, including without limitation the rights to\n",
    "# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n",
    "# the Software, and to permit persons to whom the Software is furnished to do so,\n",
    "# subject to the following conditions:\n",
    "\n",
    "# The above copyright notice and this permission notice shall be included in all\n",
    "# copies or substantial portions of the Software.\n",
    "\n",
    "# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n",
    "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n",
    "# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n",
    "# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\n",
    "# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n",
    "# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tutorial: Feature Engineering for Recommender Systems\n",
    "\n",
    "# 4. Feature Engineering - Numerical\n",
    "\n",
    "## 4.3. Gauss Rank"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import IPython\n",
    "\n",
    "import pandas as pd\n",
    "import cudf\n",
    "import numpy as np\n",
    "import cupy\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "df_train = cudf.read_parquet('../data/train.parquet')\n",
    "df_valid = cudf.read_parquet('../data/valid.parquet')\n",
    "df_test = cudf.read_parquet('../data/test.parquet')\n",
    "\n",
    "df_train['brand'] = df_train['brand'].fillna('UNKNOWN')\n",
    "df_valid['brand'] = df_valid['brand'].fillna('UNKNOWN')\n",
    "df_test['brand'] = df_test['brand'].fillna('UNKNOWN')\n",
    "\n",
    "df_train['cat_0'] = df_train['cat_0'].fillna('UNKNOWN')\n",
    "df_valid['cat_0'] = df_valid['cat_0'].fillna('UNKNOWN')\n",
    "df_test['cat_0'] = df_test['cat_0'].fillna('UNKNOWN')\n",
    "\n",
    "df_train['cat_1'] = df_train['cat_1'].fillna('UNKNOWN')\n",
    "df_valid['cat_1'] = df_valid['cat_1'].fillna('UNKNOWN')\n",
    "df_test['cat_1'] = df_test['cat_1'].fillna('UNKNOWN')\n",
    "\n",
    "df_train['cat_2'] = df_train['cat_2'].fillna('UNKNOWN')\n",
    "df_valid['cat_2'] = df_valid['cat_2'].fillna('UNKNOWN')\n",
    "df_test['cat_2'] = df_test['cat_2'].fillna('UNKNOWN')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We generate some numerical features with the feature engineering from the previous notebooks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def target_encode(train, valid, col, target, kfold=5, smooth=20, gpu=True):\n",
    "    \"\"\"\n",
    "        train:  train dataset\n",
    "        valid:  validation dataset\n",
    "        col:   column which will be encoded (in the example RESOURCE)\n",
    "        target: target column which will be used to calculate the statistic\n",
    "    \"\"\"\n",
    "    \n",
    "    # We assume that the train dataset is shuffled\n",
    "    train['kfold'] = ((train.index) % kfold)\n",
    "    # We keep the original order as cudf merge will not preserve the original order\n",
    "    if gpu:\n",
    "        train['org_sorting'] = cupy.arange(len(train), dtype=\"int32\")\n",
    "    else:\n",
    "        train['org_sorting'] = np.arange(len(train), dtype=\"int32\")\n",
    "    # We create the output column, we fill with 0\n",
    "    col_name = '_'.join(col)\n",
    "    train['TE_' + col_name] = 0.\n",
    "    for i in range(kfold):\n",
    "        ###################################\n",
    "        # filter for out of fold\n",
    "        # calculate the mean/counts per group category\n",
    "        # calculate the global mean for the oof\n",
    "        # calculate the smoothed TE\n",
    "        # merge it to the original dataframe\n",
    "        ###################################\n",
    "        \n",
    "        df_tmp = train[train['kfold']!=i]\n",
    "        mn = df_tmp[target].mean()\n",
    "        df_tmp = df_tmp[col + [target]].groupby(col).agg(['mean', 'count']).reset_index()\n",
    "        df_tmp.columns = col + ['mean', 'count']\n",
    "        df_tmp['TE_tmp'] = ((df_tmp['mean']*df_tmp['count'])+(mn*smooth)) / (df_tmp['count']+smooth)\n",
    "        df_tmp_m = train[col + ['kfold', 'org_sorting', 'TE_' + col_name]].merge(df_tmp, how='left', left_on=col, right_on=col).sort_values('org_sorting')\n",
    "        df_tmp_m.loc[df_tmp_m['kfold']==i, 'TE_' + col_name] = df_tmp_m.loc[df_tmp_m['kfold']==i, 'TE_tmp']\n",
    "        train['TE_' + col_name] = df_tmp_m['TE_' + col_name].fillna(mn).values\n",
    "\n",
    "    \n",
    "    ###################################\n",
    "    # calculate the mean/counts per group for the full training dataset\n",
    "    # calculate the global mean\n",
    "    # calculate the smoothed TE\n",
    "    # merge it to the original dataframe\n",
    "    # drop all temp columns\n",
    "    ###################################    \n",
    "    \n",
    "    df_tmp = train[col + [target]].groupby(col).agg(['mean', 'count']).reset_index()\n",
    "    mn = train[target].mean()\n",
    "    df_tmp.columns = col + ['mean', 'count']\n",
    "    df_tmp['TE_tmp'] = ((df_tmp['mean']*df_tmp['count'])+(mn*smooth)) / (df_tmp['count']+smooth)\n",
    "    if gpu:\n",
    "        valid['org_sorting'] = cupy.arange(len(valid), dtype=\"int32\")\n",
    "    else:\n",
    "        valid['org_sorting'] = np.arange(len(valid), dtype=\"int32\")\n",
    "    df_tmp_m = valid[col + ['org_sorting']].merge(df_tmp, how='left', left_on=col, right_on=col).sort_values('org_sorting')\n",
    "    valid['TE_' + col_name] = df_tmp_m['TE_tmp'].fillna(mn).values\n",
    "    \n",
    "    valid = valid.drop('org_sorting', axis=1)\n",
    "    train = train.drop('kfold', axis=1)\n",
    "    train = train.drop('org_sorting', axis=1)\n",
    "    return(train, valid)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "cats = [['cat_0'], ['cat_1'], ['cat_2'], ['cat_0', 'cat_1', 'cat_2'], ['ts_hour'], ['ts_weekday'], ['ts_weekday', 'ts_hour', 'cat_2', 'brand']]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "for cat in cats:\n",
    "    df_train, df_valid = target_encode(df_train, df_valid, cat, 'target')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "cats = ['brand', 'user_id', 'product_id', 'cat_0', 'cat_1', 'cat_2']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def count_encode(train, valid, col, gpu=True):\n",
    "    \"\"\"\n",
    "        train:  train dataset\n",
    "        valid:  validation dataset\n",
    "        col:    column which will be count encoded (in the example RESOURCE)\n",
    "    \"\"\"\n",
    "    # We keep the original order as cudf merge will not preserve the original order\n",
    "    if gpu:\n",
    "        train['org_sorting'] = cupy.arange(len(train), dtype=\"int32\")\n",
    "    else:\n",
    "        train['org_sorting'] = np.arange(len(train), dtype=\"int32\")\n",
    "    \n",
    "    train_tmp = train[col].value_counts().reset_index()\n",
    "    train_tmp.columns = [col,  'CE_' + col]\n",
    "    df_tmp = train[[col, 'org_sorting']].merge(train_tmp, how='left', left_on=col, right_on=col).sort_values('org_sorting')\n",
    "    train['CE_' + col] = df_tmp['CE_' + col].fillna(0).values\n",
    "        \n",
    "    if gpu:\n",
    "        valid['org_sorting'] = cupy.arange(len(valid), dtype=\"int32\")\n",
    "    else:\n",
    "        valid['org_sorting'] = np.arange(len(valid), dtype=\"int32\")\n",
    "    df_tmp = valid[[col, 'org_sorting']].merge(train_tmp, how='left', left_on=col, right_on=col).sort_values('org_sorting')\n",
    "    valid['CE_' + col] = df_tmp['CE_' + col].fillna(0).values\n",
    "    \n",
    "    valid = valid.drop('org_sorting', axis=1)\n",
    "    train = train.drop('org_sorting', axis=1)\n",
    "    return(train, valid)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 644 ms, sys: 1.27 s, total: 1.92 s\n",
      "Wall time: 1.92 s\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "for cat in cats:\n",
    "    df_train, df_valid = count_encode(df_train, df_valid, cat, gpu=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>event_time</th>\n",
       "      <th>event_type</th>\n",
       "      <th>product_id</th>\n",
       "      <th>brand</th>\n",
       "      <th>price</th>\n",
       "      <th>user_id</th>\n",
       "      <th>user_session</th>\n",
       "      <th>target</th>\n",
       "      <th>cat_0</th>\n",
       "      <th>cat_1</th>\n",
       "      <th>...</th>\n",
       "      <th>TE_cat_0_cat_1_cat_2</th>\n",
       "      <th>TE_ts_hour</th>\n",
       "      <th>TE_ts_weekday</th>\n",
       "      <th>TE_ts_weekday_ts_hour_cat_2_brand</th>\n",
       "      <th>CE_brand</th>\n",
       "      <th>CE_user_id</th>\n",
       "      <th>CE_product_id</th>\n",
       "      <th>CE_cat_0</th>\n",
       "      <th>CE_cat_1</th>\n",
       "      <th>CE_cat_2</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>2019-12-01 00:00:28 UTC</td>\n",
       "      <td>cart</td>\n",
       "      <td>17800342</td>\n",
       "      <td>zeta</td>\n",
       "      <td>66.90</td>\n",
       "      <td>550465671</td>\n",
       "      <td>22650a62-2d9c-4151-9f41-2674ec6d32d5</td>\n",
       "      <td>0</td>\n",
       "      <td>computers</td>\n",
       "      <td>desktop</td>\n",
       "      <td>...</td>\n",
       "      <td>0.280155</td>\n",
       "      <td>0.305423</td>\n",
       "      <td>0.410060</td>\n",
       "      <td>0.301241</td>\n",
       "      <td>10859</td>\n",
       "      <td>9</td>\n",
       "      <td>743</td>\n",
       "      <td>372964</td>\n",
       "      <td>51652</td>\n",
       "      <td>5058060</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2019-12-01 00:00:39 UTC</td>\n",
       "      <td>cart</td>\n",
       "      <td>3701309</td>\n",
       "      <td>polaris</td>\n",
       "      <td>89.32</td>\n",
       "      <td>543733099</td>\n",
       "      <td>a65116f4-ac53-4a41-ad68-6606788e674c</td>\n",
       "      <td>0</td>\n",
       "      <td>appliances</td>\n",
       "      <td>environment</td>\n",
       "      <td>...</td>\n",
       "      <td>0.350069</td>\n",
       "      <td>0.305249</td>\n",
       "      <td>0.410061</td>\n",
       "      <td>0.333539</td>\n",
       "      <td>50273</td>\n",
       "      <td>56</td>\n",
       "      <td>12</td>\n",
       "      <td>1527338</td>\n",
       "      <td>287043</td>\n",
       "      <td>213674</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2019-12-01 00:00:40 UTC</td>\n",
       "      <td>cart</td>\n",
       "      <td>3701309</td>\n",
       "      <td>polaris</td>\n",
       "      <td>89.32</td>\n",
       "      <td>543733099</td>\n",
       "      <td>a65116f4-ac53-4a41-ad68-6606788e674c</td>\n",
       "      <td>0</td>\n",
       "      <td>appliances</td>\n",
       "      <td>environment</td>\n",
       "      <td>...</td>\n",
       "      <td>0.351989</td>\n",
       "      <td>0.305235</td>\n",
       "      <td>0.410059</td>\n",
       "      <td>0.319065</td>\n",
       "      <td>50273</td>\n",
       "      <td>56</td>\n",
       "      <td>12</td>\n",
       "      <td>1527338</td>\n",
       "      <td>287043</td>\n",
       "      <td>213674</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>2019-12-01 00:00:41 UTC</td>\n",
       "      <td>cart</td>\n",
       "      <td>3701309</td>\n",
       "      <td>polaris</td>\n",
       "      <td>89.32</td>\n",
       "      <td>543733099</td>\n",
       "      <td>a65116f4-ac53-4a41-ad68-6606788e674c</td>\n",
       "      <td>0</td>\n",
       "      <td>appliances</td>\n",
       "      <td>environment</td>\n",
       "      <td>...</td>\n",
       "      <td>0.351410</td>\n",
       "      <td>0.305370</td>\n",
       "      <td>0.410061</td>\n",
       "      <td>0.333539</td>\n",
       "      <td>50273</td>\n",
       "      <td>56</td>\n",
       "      <td>12</td>\n",
       "      <td>1527338</td>\n",
       "      <td>287043</td>\n",
       "      <td>213674</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>2019-12-01 00:01:56 UTC</td>\n",
       "      <td>cart</td>\n",
       "      <td>1004767</td>\n",
       "      <td>samsung</td>\n",
       "      <td>235.60</td>\n",
       "      <td>579970209</td>\n",
       "      <td>c6946211-ce70-4228-95ce-fd7fccdde63c</td>\n",
       "      <td>0</td>\n",
       "      <td>construction</td>\n",
       "      <td>tools</td>\n",
       "      <td>...</td>\n",
       "      <td>0.460389</td>\n",
       "      <td>0.305449</td>\n",
       "      <td>0.410061</td>\n",
       "      <td>0.466269</td>\n",
       "      <td>2323417</td>\n",
       "      <td>9</td>\n",
       "      <td>317711</td>\n",
       "      <td>3363367</td>\n",
       "      <td>3307872</td>\n",
       "      <td>3172781</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 32 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                event_time event_type  product_id    brand   price    user_id  \\\n",
       "0  2019-12-01 00:00:28 UTC       cart    17800342     zeta   66.90  550465671   \n",
       "1  2019-12-01 00:00:39 UTC       cart     3701309  polaris   89.32  543733099   \n",
       "2  2019-12-01 00:00:40 UTC       cart     3701309  polaris   89.32  543733099   \n",
       "3  2019-12-01 00:00:41 UTC       cart     3701309  polaris   89.32  543733099   \n",
       "4  2019-12-01 00:01:56 UTC       cart     1004767  samsung  235.60  579970209   \n",
       "\n",
       "                           user_session  target         cat_0        cat_1  \\\n",
       "0  22650a62-2d9c-4151-9f41-2674ec6d32d5       0     computers      desktop   \n",
       "1  a65116f4-ac53-4a41-ad68-6606788e674c       0    appliances  environment   \n",
       "2  a65116f4-ac53-4a41-ad68-6606788e674c       0    appliances  environment   \n",
       "3  a65116f4-ac53-4a41-ad68-6606788e674c       0    appliances  environment   \n",
       "4  c6946211-ce70-4228-95ce-fd7fccdde63c       0  construction        tools   \n",
       "\n",
       "   ... TE_cat_0_cat_1_cat_2  TE_ts_hour  TE_ts_weekday  \\\n",
       "0  ...             0.280155    0.305423       0.410060   \n",
       "1  ...             0.350069    0.305249       0.410061   \n",
       "2  ...             0.351989    0.305235       0.410059   \n",
       "3  ...             0.351410    0.305370       0.410061   \n",
       "4  ...             0.460389    0.305449       0.410061   \n",
       "\n",
       "   TE_ts_weekday_ts_hour_cat_2_brand  CE_brand  CE_user_id  CE_product_id  \\\n",
       "0                           0.301241     10859           9            743   \n",
       "1                           0.333539     50273          56             12   \n",
       "2                           0.319065     50273          56             12   \n",
       "3                           0.333539     50273          56             12   \n",
       "4                           0.466269   2323417           9         317711   \n",
       "\n",
       "   CE_cat_0  CE_cat_1  CE_cat_2  \n",
       "0    372964     51652   5058060  \n",
       "1   1527338    287043    213674  \n",
       "2   1527338    287043    213674  \n",
       "3   1527338    287043    213674  \n",
       "4   3363367   3307872   3172781  \n",
       "\n",
       "[5 rows x 32 columns]"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_train.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Theory"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the previous notebook, we discussed how important *Normalization* is for neural networks. We learned some basic strategies for normalizing numerical features. In this notebook, we will provide another normalization technique, called <b>*Gauss Rank*</b>.<br><br>\n",
    "\n",
    "<b>*Gauss Rank*</b> transforms any arbitrary distribution to a Gaussian normal distribution by\n",
    "1. Compute the rank (or sort the values ascending)\n",
    "2. Scale the values linearly from -1 to +1\n",
    "3. Apply the erfinv function"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src='../images/gaussrank.png' width=50%>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Source: https://medium.com/rapids-ai/gauss-rank-transformation-is-100x-faster-with-rapids-and-cupy-7c947e3397da"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "import cupy as cp\n",
    "from cupyx.scipy.special import erfinv\n",
    "import cudf as gd\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from scipy.special import erfinv as sp_erfinv"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "def gaussrank_cpu(data, epsilon = 1e-6):\n",
    "    r_cpu = data.argsort().argsort()\n",
    "    r_cpu = (r_cpu/r_cpu.max()-0.5)*2 # scale to (-1,1)\n",
    "    r_cpu = np.clip(r_cpu,-1+epsilon,1-epsilon)\n",
    "    r_cpu = sp_erfinv(r_cpu)\n",
    "    return(r_cpu)\n",
    "\n",
    "def gaussrank_gpu(data, epsilon = 1e-6):\n",
    "    r_gpu = data.argsort().argsort()\n",
    "    r_gpu = (r_gpu/r_gpu.max()-0.5)*2 # scale to (-1,1)\n",
    "    r_gpu = cp.clip(r_gpu,-1+epsilon,1-epsilon)\n",
    "    r_gpu = erfinv(r_gpu)\n",
    "    return(r_gpu)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'Histogram Gauss Rank')"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 1152x216 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(1, 2, figsize=(16,3))\n",
    "col = 'CE_product_id'\n",
    "data_sample = df_train[col].sample(frac=0.01)\n",
    "axs[0].hist(data_sample.to_pandas().values, bins=50)\n",
    "axs[1].hist(cp.asnumpy(gaussrank_gpu(df_train[col].values)), bins=50)\n",
    "axs[0].set_title('Histogram non-normalized')\n",
    "axs[1].set_title('Histogram Gauss Rank')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Practice"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, it is your turn.<br><br>\n",
    "\n",
    "**ToDo**:\n",
    "<li>Normalize the features price, TE_ts_weekday_ts_hour_cat_2_brand and CE_cat_2 with GaussRank<br>\n",
    "<li>Plot the non-normalized and normalized values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 1152x648 with 6 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "############### Solution ###############\n",
    "\n",
    "fig, axs = plt.subplots(3, 2, figsize=(16,9))\n",
    "for i, col in enumerate(['price', 'TE_ts_weekday_ts_hour_cat_2_brand', 'CE_cat_2']):\n",
    "    data_sample = df_train[col].sample(frac=0.01)\n",
    "    axs[i, 0].hist(data_sample.to_pandas(), bins=50)\n",
    "    axs[i, 1].hist(cp.asnumpy(gaussrank_gpu(data_sample.values)), bins=50)\n",
    "    if i==0:\n",
    "        axs[i, 0].set_title('Histogram non-normalized')\n",
    "        axs[i, 1].set_title('Histogram Gauss Rank')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "############### Solution End ###########"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Optimization"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's compare a CPU with the GPU version."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_cpu = df_train['TE_ts_weekday_ts_hour_cat_2_brand'].to_pandas().values\n",
    "data_gpu = df_train['TE_ts_weekday_ts_hour_cat_2_brand'].values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 3.76 s, sys: 176 ms, total: 3.94 s\n",
      "Wall time: 3.93 s\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([-0.49958087, -0.31557098, -0.39975219, ..., -0.33439334,\n",
       "        0.06216684, -0.5348664 ])"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%%time \n",
    "\n",
    "gaussrank_cpu(data_cpu)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 36 ms, sys: 64 ms, total: 100 ms\n",
      "Wall time: 100 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([-0.50025555, -0.31601779, -0.40005142, ..., -0.3343783 ,\n",
       "        0.06505958, -0.53484046])"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%%time \n",
    "\n",
    "gaussrank_gpu(data_gpu)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In our experiments, we achieved a speedup of 80.3x."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We shutdown the kernel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'status': 'ok', 'restart': False}"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "app = IPython.Application.instance()\n",
    "app.kernel.do_shutdown(False)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
